#include <iostream>
#include "revision.h"
#include "macros.h"
#include "M_Code_workspace.h"
#include "cbbuildcfg.h"
#include "cbhelper.h"
#include "stlfutils.h"
#include "tinyxml.h"

CWorkspaceUnit::CWorkspaceUnit( void ) {
  Clear();
}

CWorkspaceUnit::~CWorkspaceUnit( void ) {
  Clear();
}

void CWorkspaceUnit::Clear( void ) {
  m_FileName.Clear();
  m_Depends.Clear();
  M_Code_Project.Clear();
  m_Weight = 0;
}

void CWorkspaceUnit::Read( const TiXmlElement* UnitRoot ) {
  m_FileName = UnitRoot->Attribute( "filename" );
  const TiXmlNode *_depends = UnitRoot->FirstChild( "Depends" );
  while( 0 != _depends ) {
    const TiXmlElement *depends = _depends->ToElement();
    if( 0 != depends ) {
      char *value = ( char * )depends->Attribute( "filename" );
      if( 0 != value ) {
        m_Depends.Insert( value );
      }
    }
    _depends = UnitRoot->IterateChildren( _depends );
  }
  m_Depends.RemoveDuplicates();
  //m_Depends.RemoveEmpty();
}

void CWorkspaceUnit::Show( void ) {
  #ifdef SHOW_MODE_ONELINE
  std::cout << m_FileName.GetString();
  std::cout << ", Weight: " << m_Weight;
  std::cout << ", Dependencies: " << m_Depends.GetCount() << std::endl;
  for( int i = 0, n = m_Depends.GetCount(); i < n; i++ ) {
    std::cout << "Dependency #" << ( i + 1 ) << ": " << m_Depends[i].GetString() << std::endl;
  }
  #else
  std::cout << "Filename: " << m_FileName.GetString() << std::endl;
  std::cout << "Weight: " << m_Weight << std::endl;
  ShowStringList( "Dependencies", "Dependency", m_Depends );
  #endif
}

bool CWorkspaceUnit::LoadProject( const CString& WorkspacePath ) {
  return M_Code_Project.LoadProject( JoinPaths( WorkspacePath, m_FileName ) );
}


CCodeBlocksWorkspace::CCodeBlocksWorkspace( void ) {
  Clear();
}

CCodeBlocksWorkspace::~CCodeBlocksWorkspace( void ) {
  Clear();
}

int CCodeBlocksWorkspace::CalculateProjectWeight( const size_t Index ) {
  CWorkspaceUnit *unit = m_Units[Index];
  if( unit->m_Depends.GetCount() ) {
    if( unit->m_Weight ) {
      return unit->m_Weight;
    }
    int weight = 0;
    for( int j = 0, nj = unit->m_Depends.GetCount(); j < nj; j++ ) {
      CString dependency = unit->m_Depends[j];
      for( size_t i = 0, ni = m_Units.size(); i < ni; i++ ) {
        if( i != Index ) {
          if( unit->m_FileName == dependency ) {
            weight += CalculateProjectWeight( i );
          }
        }
      }
    }
    weight++;
    return weight;
  }
  return 0;
}

void CCodeBlocksWorkspace::ResolveProjectDependencies( void ) {
  for( size_t i = 0; i < m_Units.size(); i++ ) {
    m_Units[i]->m_Weight = CalculateProjectWeight( i );
  }
}

class CProjectWeightComparison {
  public:
    bool operator()( CWorkspaceUnit *const &AUnit, CWorkspaceUnit *const &BUnit ) {
      return ( AUnit->m_Weight < BUnit->m_Weight );
    }
};

void CCodeBlocksWorkspace::SortProjectsByWeight( void ) {
  CProjectWeightComparison cmp;
  std::sort( m_Units.begin(), m_Units.end(), cmp );
}

void CCodeBlocksWorkspace::Clear() {
  m_Title.Clear();
  for( size_t i = 0; i < m_Units.size(); i++ ) {
    delete m_Units[i];
  }
  m_Units.clear();
  m_TargetNames.Clear();
  m_MakefileNames.Clear();
  m_MakefilePaths.Clear();
  m_TargetDeps.Clear();
  m_MakefileText.Clear();
  m_Makefile.Clear();
}

void CCodeBlocksWorkspace::Read( const TiXmlElement* WorkspaceRoot ) {
  const TiXmlNode *_workspace = WorkspaceRoot->FirstChild( "Workspace" );
  if( 0 != _workspace ) {
    const TiXmlElement *workspace = _workspace->ToElement();
    if( 0 != workspace ) {
      m_Title = workspace->Attribute( "title" );
      TiXmlNode *_project = ( TiXmlNode * )_workspace->FirstChild( "Project" );
      while( 0 != _project ) {
        TiXmlElement* project = _project->ToElement();
        if( 0 != project ) {
          CWorkspaceUnit *unit = new CWorkspaceUnit();
          unit->Read( project );
          m_Units.push_back( unit );
        }
        _project = ( TiXmlNode * )_workspace->IterateChildren( _project );
      }
    }
  }
}

bool CCodeBlocksWorkspace::LoadWorkspace( const CString& FileName ) {
  bool result = false;
  TiXmlDocument cbw;
  result = cbw.LoadFile( FileName.GetCString() );
  if( !result ) {
    return false;
  }
  Clear();
  const TiXmlElement *root = cbw.RootElement();
  if( 0 == strcmp( root->Value(), "CodeBlocks_workspace_file" ) ) {
    Read( root );
    result = true;
  }
  //
  return result;
}

bool CCodeBlocksWorkspace::LoadWorkspaceProjects( const CString& WorkspacePath ) {
  bool result = true;
  for( size_t i = 0; i < m_Units.size(); i++ ) {
    result &= m_Units[i]->LoadProject( WorkspacePath );
  }
  // resolve project dependencies and sort projects by weight
  ResolveProjectDependencies();
  SortProjectsByWeight();
  return result;
}

void CCodeBlocksWorkspace::Show( const bool ShowProjects ) {
  std::cout << "Workspace title: " << m_Title.GetString() << std::endl;
  std::cout << "Projects: " << m_Units.size() << std::endl;
  for( size_t i = 0; i < m_Units.size(); i++ ) {
    std::cout << "Project #" << ( i + 1 ) << ": ";
    #ifndef SHOW_MODE_ONELINE
    std::cout << std::endl;
    #endif
    m_Units[i]->Show();
    if( ShowProjects ) {
      m_Units[i]->m_Project.Show();
    }
  }
}

bool CCodeBlocksWorkspace::GenerateMakefile
( const CString& FileName, CCodeBlocksBuildConfig& Config ) {
  // generate individual makefiles for projects
  CString cwd = GetCurrentDir();
  CString workspace_path = ExtractFilePath( FileName );
  if( !workspace_path.IsEmpty() ) {
    ChangeDir( workspace_path );
  }
  //
  m_TargetNames.Clear();
  m_MakefileNames.Clear();
  m_TargetDeps.Clear();
  for( size_t i = 0; i < m_Units.size(); i++ ) {
    CString project_name = m_Units[i]->m_FileName;
    CString makefile_path = ExtractFilePath( project_name );
    CString makefile_pathname = project_name + ".mak";
    CString target_name = ChangeFileExt( project_name, "" );
    CString makefile_name = ExtractFileName( makefile_pathname );
    target_name = CCodeBlocksProject::DecorateTargetName( target_name, Config.TargetNameCase() );
    CString target_deps;
    for( int j = 0; j < m_Units[i]->m_Depends.GetCount(); j++ ) {
      CString dep_name = ChangeFileExt( m_Units[i]->m_Depends[j], "" );
      if( j > 0 ) {
        target_deps += " ";
      }
      target_deps += CCodeBlocksProject::DecorateTargetName( dep_name, Config.TargetNameCase() );
    }
    m_TargetDeps.Insert( target_deps );
    m_TargetNames.Insert( target_name );
    m_MakefilePaths.Insert( makefile_path );
    m_MakefileNames.Insert( makefile_name );
    makefile_pathname = MakeNativePath( makefile_pathname );
    m_Units[i]->m_Project.GenerateMakefile( makefile_pathname, Config );
  }
  // generate workspace makefile
  int active_platforms = 0;
  for( size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++ ) {
    if( Config.Platforms().Platform( pi )->Active() ) {
      active_platforms++;
    }
  }
  bool single_platform = ( 1 == active_platforms );
  for( size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++ ) {
    CPlatform *pl = Config.Platforms().Platform( pi );
    if( !pl->Active() ) {
      continue;
    }
    CString makefile_path = ExtractFilePath( FileName );
    CString makefile_name = ExtractFileName( FileName );
    CString platform_suffix;
    if( !single_platform ) {
      platform_suffix = "." + LowerCase( pl->Name() );
    }
    makefile_name += platform_suffix;
    ChangeDir( makefile_path );
    m_Makefile.Clear();
    int section = 0;
    const int header_width = 80;
    m_Makefile.Header().Insert( FillStr( "#", '-', "#", header_width ) );
    #ifdef REVISION_NUMBER
    {
      CString rn = IntegerToString( REVISION_NUMBER );
      CString line = FillStr( "# This makefile was generated by 'cbp2make' tool rev." + rn, ' ', "#", header_width );
      m_Makefile.Header().Insert( line );
    }
    #else
    CString line = FillStr( "# This makefile was generated by 'cbp2make' tool rev.", ' ', "#", header_width )
    #endif
    m_Makefile.Header().Insert( FillStr( "#", '-', "#", header_width ) );
    m_Makefile.Header().Insert( "" );
    section++;
    CString line = pl->EvalWorkDir();
    m_Makefile.AddMacro( "WRKDIR", line, section );
    m_Makefile.AddMacro( "MAKE", pl->Tool_Make(), section );
    section++;
    CMakefileRule& rule_all = m_Makefile.AddRule( "all", section );
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      rule_all.Dependencies().Insert( m_TargetNames[i] );
    }
    section++;
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      CString makefile_path = m_MakefilePaths[i];
      CMakefileRule& rule_target = m_Makefile.AddRule( m_TargetNames[i], section );
      rule_target.Dependencies().Insert( m_TargetDeps[i] );
      line = "$(MAKE)";
      if( !makefile_path.IsEmpty() ) {
        line += " -C " + pl->ProtectPath( pl->Pd( makefile_path ), Config.QuotePathMode() );
      }
      line += " all -f " + pl->ProtectPath( pl->Pd( m_MakefileNames[i] ) + platform_suffix, Config.QuotePathMode() );
      rule_target.Commands().Insert( line );
    }
    section++;
    CMakefileRule& rule_clean = m_Makefile.AddRule( "clean", section );
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      rule_clean.Dependencies().Insert( "clean_" + m_TargetNames[i] );
    }
    section++;
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      CString makefile_path = m_MakefilePaths[i];
      CMakefileRule& rule_clean_target = m_Makefile.AddRule( "clean_" + m_TargetNames[i], section );
      line = "$(MAKE)";
      if( !makefile_path.IsEmpty() ) {
        line += " -C " + pl->ProtectPath( pl->Pd( makefile_path ), Config.QuotePathMode() );
      }
      line += " clean -f " + pl->ProtectPath( pl->Pd( m_MakefileNames[i] ) + platform_suffix, Config.QuotePathMode() );
      rule_clean_target.Commands().Insert( line );
    }
    CStringList& text = m_Makefile.Update();
    text.SaveToFile( makefile_name );
    m_Makefile.Clear();
    ChangeDir( cwd );
  }
  return true;
}

void CCodeBlocksWorkspace::GenerateMakefileText( const CString& FileName,
    CCodeBlocksBuildConfig& Config ) {
  CString cwd = GetCurrentDir();
  CString workspace_path = ExtractFilePath( FileName );
  if( !workspace_path.IsEmpty() ) {
    ChangeDir( workspace_path );
  }
  //std::cout<<"Workspace path: "<<workspace_path.GetCString()<<std::endl;
  m_TargetNames.Clear();
  m_MakefileNames.Clear();
  for( size_t i = 0; i < m_Units.size(); i++ ) {
    CString project_name = m_Units[i]->m_FileName;
    CString makefile_path = JoinPaths( workspace_path, ExtractFilePath( project_name ) );
    CString makefile_pathname = JoinPaths( workspace_path, project_name + ".mak" );
    CString target_name = LowerCase( ChangeFileExt( project_name, "" ) );
    CString makefile_name = ExtractFileName( makefile_pathname );
    target_name = CCodeBlocksProject::DecorateTargetName( target_name );
    m_TargetNames.Insert( target_name );
    m_MakefilePaths.Insert( makefile_path );
    m_MakefileNames.Insert( makefile_name );
    makefile_pathname = MakeNativePath( makefile_pathname );
    m_Units[i]->m_Project.GenerateMakefile( makefile_pathname, Config );
  }
  int active_platforms = 0;
  for( size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++ ) {
    if( Config.Platforms().Platform( pi )->Active() ) {
      active_platforms++;
    }
  }
  bool single_platform = ( 1 == active_platforms );
  for( size_t pi = 0, pn = Config.Platforms().GetCount(); pi < pn; pi++ ) {
    CPlatform *pl = Config.Platforms().Platform( pi );
    if( !pl->Active() ) {
      continue;
    }
    CString makefile_path = ExtractFilePath( FileName );
    CString makefile_name = ExtractFileName( FileName );
    CString platform_suffix;
    if( !single_platform ) {
      platform_suffix = "." + LowerCase( pl->Name() );
    }
    makefile_name += platform_suffix;
    m_MakefileText.Clear();
    m_MakefileText.Insert( "#------------------------------------------------------------------------------#" );
    #ifdef REVISION_NUMBER
    {
      CString s = "# This makefile was generated by 'cbp2make' tool rev.                          #";
      CString n = IntegerToString( REVISION_NUMBER );
      int o = FindStr( s, "rev." ) + 4;
      for( int i = 0; i < n.GetLength(); i++ ) {
        s.SetChar( i + o, n[i] );
      }
      m_MakefileText.Insert( s );
    }
    #else
    m_MakefileText.Insert( "# This makefile was generated by 'cbp2make' tool                               #" );
    #endif
    m_MakefileText.Insert( "#------------------------------------------------------------------------------#" );
    m_MakefileText.Insert( "" );
    CString line = "WRKDIR = " + pl->EvalWorkDir();
    m_MakefileText.Insert( line );
    line = "MAKE = " + pl->Tool_Make();
    m_MakefileText.Insert( line );
    m_MakefileText.Insert( "" );
    line = "all:";
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      line.Append( " " ).Append( m_TargetNames[i] );
    }
    m_MakefileText.Insert( line );
    m_MakefileText.Insert( "" );
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      CString makefile_path = m_MakefilePaths[i];
      line = m_TargetNames[i] + ":";
      m_MakefileText.Insert( line );
      line = "\t$(MAKE)";
      if( !makefile_path.IsEmpty() ) {
        line += " -C " + pl->ProtectPath( pl->Pd( makefile_path ), Config.QuotePathMode() );
      }
      line += " all -f " + pl->ProtectPath( pl->Pd( m_MakefileNames[i] ) + platform_suffix, Config.QuotePathMode() );
      m_MakefileText.Insert( line );
      m_MakefileText.Insert( "" );
    }
    line = "clean:";
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      line.Append( " clean_" ).Append( m_TargetNames[i] );
    }
    m_MakefileText.Insert( line );
    m_MakefileText.Insert( "" );
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      CString makefile_path = m_MakefilePaths[i];
      line = "clean_" + m_TargetNames[i] + ":";
      m_MakefileText.Insert( line );
      line = "\t$(MAKE)";
      if( !makefile_path.IsEmpty() ) {
        line += " -C " + pl->ProtectPath( pl->Pd( makefile_path ), Config.QuotePathMode() );
      }
      line += " clean -f " + pl->ProtectPath( pl->Pd( m_MakefileNames[i] ) + platform_suffix, Config.QuotePathMode() );
      m_MakefileText.Insert( line );
      m_MakefileText.Insert( "" );
    }
    line = ".PHONY:";
    for( int i = 0; i < m_TargetNames.GetCount(); i++ ) {
      line.Append( " " ).Append( m_TargetNames[i] );
      line.Append( " clean_" ).Append( m_TargetNames[i] );
    }
    m_MakefileText.Insert( line );
    m_MakefileText.Insert( "" );
    m_MakefileText.SaveToFile( makefile_name );
    m_MakefileText.Clear();
    ChangeDir( cwd );
  }
}

//------------------------------------------------------------------------------
